package com.hfmcoding;

import java.util.*;
import java.io.*;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;

public class HFMCoding {
    String inFilePath;// 需要压缩的文件路径,用户选择文件
    String outFilePath;// 压缩后文件路径，根据学生的姓名学号获得
    String letterAndWeightPath;// 字符及其权重路径，根据学生的姓名学号获得
    String decompressPath;// 解压后文件路径，根据学生的姓名学号获得

    public void getInFilePath() {
        // 通过文件选择器获取输入文件的路径
        // 请更改这里txt为docx
        FileNameExtensionFilter filter = new FileNameExtensionFilter("*.txt", "txt");
        JFileChooser file = new JFileChooser();
        file.setFileFilter(filter);
        file.setAcceptAllFileFilterUsed(false);
        int result = file.showOpenDialog(null);
        if (result == JFileChooser.APPROVE_OPTION) {
            String path = file.getSelectedFile().getAbsolutePath();
            // 请在这调用文档类型转化，inFilePath为转化后的路径
            this.inFilePath = path;
        }
        return;
    }

    public void getOutFilePath() throws IOException {
        // 通过接口获取输出文件的路径
        String homeworkid = "";
        String courseid = "";
        String studentid = "";
        String classid = "";
        // 根据选择，注意//还是\
        this.outFilePath = "C:\\Users\\罗一\\Desktop\\新建文本文档 (2)-zip.txt";
        this.letterAndWeightPath = "C:\\Users\\罗一\\Desktop\\新建文本文档 (2)-dezip.txt";
        this.decompressPath = "C:\\Users\\罗一\\Desktop\\新建文本文档 (2)-out.txt";
        return;
    }

    public void readyCompress() throws IOException {
        File file1 = new File(this.outFilePath);
        if (file1.exists())
            file1.delete();
        File file2 = new File(this.letterAndWeightPath);
        if (file2.exists())
            file2.delete();
        File file3 = new File(this.decompressPath);
        if (file3.exists())
            file3.delete();
        file2.createNewFile();
        file1.createNewFile();
        file3.createNewFile();
        return;
    }

    public void readyDecompress() {
        File file1 = new File(this.decompressPath);
        if (file1.exists())
            file1.delete();
    }

    public void compress(HFMNodeList nodeList) throws IOException {
        // 压缩文件
        this.getInFilePath();
        this.getOutFilePath();
        this.readyCompress();
        OutputStream output = new FileOutputStream(this.outFilePath);
        InputStreamReader input = new InputStreamReader(new FileInputStream(inFilePath), "UTF-8");
        String bitCode = new String();// 为字符转换成的哈夫曼编码
        byte thisByte = 0, oneOrZero;
        nodeList.getHFMNode(this.inFilePath, this.letterAndWeightPath);
        HFMNode HFMTree = nodeList.getHFMTree();// 哈夫曼树
        Map<Character, Integer> codeMap = new HashMap<Character, Integer>();// 存放字符哈夫曼编码
        HFMTree.getHFMCode(codeMap);
        int thisletterInt = 0;
        // 读取待压缩文件字符转化为哈夫曼编码，每八位写入一次压缩文件
        while (thisletterInt != -1) {
            while (bitCode.length() < 8) {
                if ((thisletterInt = input.read()) != -1) {
                    char thisletter = (char) thisletterInt;
                    bitCode = bitCode + getBit(thisletter, codeMap);
                } else
                    break;
            }
            if (bitCode.length() >= 8) {
                thisByte = 0;// thisByte为将哈夫曼编码每八位写入文件
                for (int i = 0; i <= 7; i++) {
                    if (bitCode.charAt(0) == '0') {
                        oneOrZero = 0;// oneOrzero为哈夫曼编码的某一位
                        bitCode = bitCode.replaceFirst("0", "");
                    } else {
                        oneOrZero = 1;// oneOrzero为哈夫曼编码的某一位
                        bitCode = bitCode.replaceFirst("1", "");
                    }
                    thisByte = (byte) (oneOrZero << (7 - i) | thisByte);
                }
                output.write(thisByte);
            }
        }
        // 最后不足八位的补零写入
        thisByte = 0;
        int codelength = bitCode.length();
        for (int i0 = 0; i0 <= codelength - 1; i0++) {
            if (bitCode.charAt(i0) == '0')
                oneOrZero = 0;
            else
                oneOrZero = 1;
            thisByte = (byte) (oneOrZero << (7 - i0) | thisByte);
        }
        output.write(thisByte);
        output.close();
        input.close();
        return;
    }

    public String decompress(HFMNodeList nodeList) throws IOException, ClassNotFoundException {
        // 解压文件,返回解压后的路径
        this.getOutFilePath();
        this.readyDecompress();
        DataInputStream input = new DataInputStream(new FileInputStream(this.outFilePath));
        OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream(decompressPath), "UTF-8");
        nodeList.readHFMNode(this.letterAndWeightPath);
        HFMNode HFMTree = nodeList.getHFMTree();
        String bitCode = new String(), thisCode;// bitCode为待解压的哈夫曼编码
        /**
         * 调用函数解译哈夫曼编码时，只能传回一个参数，并且不能改变引用的参数
         * 故用answerString[0]存储解译的字符，answerString[1]存储改变后的bitCode
         */
        String[] answeString = new String[2];
        /**
         * 进过哈夫曼编码压缩，这个字符的哈夫曼编码不会超过原来的位数，即两位
         * 故每当bitCode中的位数少于16位时，尝试读取8位，可保证bitCode中有完整的一条哈夫曼编码
         * 即除文件结束外，维持bitCode中的编码在16位到24位
         */
        while (input.available() > 0 || bitCode.length() > 0) {// 读取哈夫曼编码并解码写入解压文件中
            while (bitCode.length() < 16 && !(thisCode = readBit(input)).equals(""))
                bitCode = bitCode + thisCode;
            answeString = HFMTree.readHFMCode(bitCode);
            char thisletter = answeString[0].charAt(0);
            bitCode = answeString[1];
            if (thisletter == '\0')
                break;
            else
                output.write(thisletter);
        }
        output.close();
        input.close();
        return this.decompressPath;
    }

    public String getBit(char thisletter, Map<Character, Integer> codeMap) {
        // 将一个字符转换为哈夫曼编码放入bitCode
        String thiscode = Integer.toBinaryString((codeMap.get(thisletter)));
        thiscode = thiscode.replaceFirst("1", "");
        return thiscode;
    }

    public String readBit(DataInputStream input) throws IOException {
        // 逐位读取8位存入bitCode成功则返回,否则返回空串
        byte oneByte;
        if (input.available() > 0) {
            oneByte = input.readByte();
            String thisCode = Integer.toBinaryString((oneByte & 0xFF) + 0x100).substring(1);
            return thisCode;
        }
        return "";
    }

    public String getFileCharsetName(String fileName) throws IOException {
        // txt文档的编码方式在文档开头有记录，可以读取到
        InputStream inputStream = new FileInputStream(fileName);
        byte[] head = new byte[3];
        inputStream.read(head);
        String encodeing = "gb2312";
        if (head[0] == -1 && head[1] == -2)
            encodeing = "UTF-16";
        else if (head[0] == -2 && head[1] == -1)
            encodeing = "Unicode";
        else if (head[0] == -17 && head[1] == -69 && head[2] == -65)
            encodeing = "UTF-8";
        inputStream.close();
        return encodeing;
    }
}